PolarDB PostgreSQL版实现了弹性跨机并行查询(ePQ)特性,能够帮助您解决原先的PolarDB PostgreSQL版在处理复杂的AP查询时会遇到的问题。
前提条件
支持的PolarDB PostgreSQL版的版本如下:
PostgreSQL 11(内核小版本1.1.28及以上)
您可通过如下语句查看PolarDB PostgreSQL版的内核小版本的版本号:
show polar_version;
背景信息
很多PolarDB PostgreSQL版的用户都有TP(Transactional Processing)和AP(Analytical Processing)共用的需求。他们期望数据库在白天处理高并发的TP请求,在夜间TP流量下降、机器负载空闲时进行AP的报表分析。但是即使这样,依然没有最大化利用空闲机器的资源。原先的PolarDB PostgreSQL版在处理复杂的AP查询时会遇到两大挑战:
单条SQL在原生PostgreSQL执行引擎下只能在单个节点上执行,无论是单机串行还是单机并行,都无法利用其他节点的CPU、内存等计算资源,只能纵向Scale Up,不能横向Scale Out。
PolarDB PostgreSQL版底层是存储池,理论上I/O吞吐是无限大的。而单条SQL在原生PostgreSQL执行引擎下只能在单个节点上执行,受限于单节点CPU和内存的瓶颈,无法充分发挥存储侧大I/O带宽的优势。
如图所示:
为了解决用户实际使用中的痛点,PolarDB PostgreSQL版实现了ePQ特性。当前业界HTAP的解决方案主要有以下三种:
TP和AP在存储和计算上完全分离。
优势:两种业务负载互不影响。
劣势:
时效性:TP的数据需要导入到AP系统中,存在一定的延迟。
成本/运维难度:增加了一套冗余的AP系统。
TP和AP在存储和计算上完全共享。
优势:成本最小化、资源利用最大化。
劣势:
计算共享会导致AP查询和TP查询同时运行时会存在相互影响。
扩展计算节点存储时,数据需要重分布,无法快速弹性Scale Out。
TP和AP在存储上共享,在计算上分离。
说明本身的存储计算分离架构天然支持此方案。
HTAP特性原理
架构特性。
基于PolarDB PostgreSQL版的存储计算分离架构,推出了分布式ePQ执行引擎,提供了跨机并行执行、弹性计算、弹性扩展的保证,使得PolarDB PostgreSQL版初步具备了HTAP的能力。其优势如下:
一体化存储:毫秒级数据新鲜度。
TP/AP共享一套存储数据,减少存储成本,提高查询时效。
TP/AP物理隔离:杜绝CPU/内存的相互影响。
单机执行引擎:在RW/RO节点,处理高并发的TP查询。
分布式ePQ执行引擎:在RO节点,处理高复杂度的AP查询。
Serverless弹性扩展:任何一个RO节点均可发起ePQ查询。
Scale Out:弹性调整ePQ的执行节点范围。
Scale Up:弹性调整ePQ的单机并行度。
消除数据倾斜、计算倾斜,充分考虑PolarDB PostgreSQL版的Buffer Pool亲和性。
如图所示:
分布式ePQ执行引擎。
PolarDB PostgreSQL版ePQ的核心是分布式ePQ执行引擎,是典型的火山模型引擎。A、B两张表先做join再做聚合输出,这也是PostgreSQL单机执行引擎的执行流程。执行流程如下所示:
在传统的mpp执行引擎中,数据被打散到不同的节点上,不同节点上的数据可能具有不同的分布属性,例如哈希分布、随机分布、复制分布等。传统的mpp执行引擎会针对不同表的数据分布特点,在执行计划中插入算子来保证上层算子对数据的分布属性无感知。
不同的是,PolarDB PostgreSQL版是共享存储架构,存储上的数据可以被所有计算节点全量访问。如果使用传统的mpp执行引擎,每个计算节点Worker都会扫描全量数据,从而得到重复的数据。同时,也没有起到扫描时分治加速的效果,并不能称得上是真正意义上的mpp引擎。
因此,在分布式ePQ执行引擎中,我们借鉴了火山模型论文中的思想,对所有扫描算子进行并发处理,引入了PxScan算子来屏蔽共享存储。PxScan算子将shared-storage的数据映射为shared-nothing的数据,通过Worker之间的协调,将目标表划分为多个虚拟分区数据块,每个Worker扫描各自的虚拟分区数据块,从而实现了跨机分布式并行扫描。
PxScan算子扫描出来的数据会通过Shuffle算子来重分布。重分布后的数据在每个Worker上如同单机执行一样,按照火山模型来执行。
Serverless弹性扩展。
传统mpp只能在指定节点发起mpp查询,因此每个节点上都只能有单个Worker扫描一张表。为了支持云原生下Serverless弹性扩展的需求,我们引入了分布式事务一致性保障。如图所示:
任意选择一个节点作为Coordinator节点,它的ReadLSN会作为约定的LSN,从所有ePQ节点的快照版本号中选择最小的版本号作为全局约定的快照版本号。通过LSN的回放等待和Global Snapshot同步机制,确保在任何一个节点发起ePQ查询时,数据和快照均能达到一致可用的状态。如图所示:
为了实现Serverless的弹性扩展,PolarDB PostgreSQL版从共享存储的特点出发,将Coordinator节点全链路上各个模块需要的外部依赖全部放至共享存储上。各个Worker节点运行时需要的参数也会通过控制链路从Coordinator节点同步过来,从而使Coordinator节点和Worker节点全链路无状态化(Stateless)。
基于以上两点设计,PolarDB PostgreSQL版的弹性扩展具备了以下几大优势:
任何节点都可以成为Coordinator节点,解决了传统mpp数据库Coordinator节点的单点问题。
PolarDB PostgreSQL版可以横向Scale Out(计算节点数量),也可以纵向Scale Up(单节点并行度),且弹性扩展即时生效,不需要重新分布数据。
允许业务有更多的弹性调度策略,不同的业务域可以运行在不同的节点集合上。业务域1的SQL可以选择RO1和RO2节点来执行AP查询,业务域2的SQL可以选择使用RO3和RO4节点来执行AP查询,两个业务域使用的计算节点可以实现弹性调度。
消除倾斜。
倾斜是传统mpp固有的问题,其根本原因主要是数据分布倾斜和数据计算倾斜。
数据分布倾斜通常由数据打散不均衡导致,在PostgreSQL中还会由于大对象Toast表存储引入一些不可避免的数据分布不均衡问题。
计算倾斜通常由于不同节点上并发的事务、Buffer Pool、网络、I/O抖动导致。
倾斜会导致传统mpp在执行时出现木桶效应,执行完成时间受制于执行最慢的子任务。
PolarDB PostgreSQL版设计并实现了自适应扫描机制。如下图所示,采用Coordinator节点来协调Worker节点的工作模式。在扫描数据时,Coordinator节点会在内存中创建一个任务管理器,根据扫描任务对Worker节点进行调度。Coordinator节点内部分为两个线程。
Data线程主要负责服务数据链路、收集汇总元组。
Control线程负责服务控制链路、控制每一个扫描算子的扫描进度。
扫描进度较快的Worker能够扫描多个数据块。如上图中RO1与RO3的Worker各自扫描了4个数据块, RO2由于计算倾斜可以扫描更多数据块,因此它最终扫描了6个数据块。
PolarDB PostgreSQL版ePQ的自适应扫描机制还充分考虑了PostgreSQL的Buffer Pool亲和性,保证每个Worker尽可能扫描固定的数据块,从而最大化命中Buffer Pool的概率,降低I/O开销。
TPC-H性能对比
单机并行与分布式ePQ对比。
使用256 GB内存的16个PolarDB PostgreSQL版集群作为RO节点,搭建1 TB的TPC-H环境进行对比测试。相较于单机并行,分布式ePQ并行充分利用了所有RO节点的计算资源和底层共享存储的I/O带宽,从根本上解决了前文提及的HTAP诸多挑战。在TPC-H的22条SQL中,有3条SQL加速了60多倍,19条SQL加速了10多倍,平均加速23倍。如图所示:
测试弹性扩展计算资源带来的性能变化。通过增加CPU的总核心数,从16核增加到128核,TPC-H的总运行时间线性提升,每条SQL的执行速度也呈线性提升,这也验证了PolarDB PostgreSQL版ePQ Serverless弹性扩展的特点。如图所示:
在测试中发现,当CPU的总核数增加到256核时,性能提升不再明显。原因是此时PolarDB PostgreSQL版共享存储的I/O带宽已经达到100%,成为了瓶颈。
PolarDB与传统mpp数据库对比。
同样使用256 GB内存的16个节点,将PolarDB PostgreSQL版的分布式ePQ执行引擎与传统数据库的mpp执行引擎进行对比。
在1 TB的TPC-H数据上,当保持与传统mpp数据库相同单机并行度的情况下(多机单进程),PolarDB PostgreSQL版的性能是传统mpp数据库的90%。其中最本质的原因是传统mpp数据库的数据默认是哈希分布的,当两张表的join key是各自的分布键时,可以不用shuffle直接进行本地的Wise Join。而PolarDB PostgreSQL版的底层是共享存储池,PxScan算子并行扫描出来的数据等价于随机分布,必须进行shuffle重分布以后才能像传统mpp数据库一样进行后续的处理。因此,TPC-H涉及到表连接时,PolarDB PostgreSQL版相比传统mpp数据库多了一次网络shuffle的开销。如图所示:
PolarDB PostgreSQL版分布式ePQ执行引擎能够进行弹性扩展,数据无需重分布。因此,在有限的16台机器上执行ePQ时,PolarDB PostgreSQL版还可以继续扩展单机并行度,充分利用每台机器的资源。当PolarDB PostgreSQL版的单机并行度为8时,它的性能是传统mpp数据库的5-6倍;当PolarDB PostgreSQL版的单机并行度呈线性增加时,PolarDB PostgreSQL版的总体性能也呈线性增加。只需要修改配置参数,就可以即时生效。
功能特性
Parallel Query并行查询。
经过持续迭代的研发,目前PolarDB PostgreSQL版ePQ在Parallel Query上支持的功能特性主要有五大部分:
基础算子全支持:扫描/连接/聚合/子查询等算子。
共享存储算子优化:包括Shuffle算子共享、SharedSeqScan共享、SharedIndexScan算子等。其中SharedSeqScan共享、SharedIndexScan共享是指,在大表join小表时,小表采用类似于复制表的机制来减少广播开销,进而提升性能。
分区表支持:不仅包括对Hash/Range/List三种分区方式的完整支持,还包括对多级分区静态裁剪、分区动态裁剪的支持。除此之外,PolarDB PostgreSQL版分布式ePQ执行引擎还支持分区表的Partition Wise Join。
并行度弹性控制:包括全局级别、表级别、会话级别、查询级别的并行度控制。
Serverless弹性扩展:不仅包括任意节点发起ePQ、ePQ节点范围内的任意组合,还包括集群拓扑信息的自动维护以及支持共享存储模式、主备库模式和三节点模式。
Parallel DML。
基于PolarDB PostgreSQL版读写分离架构和ePQ Serverless弹性扩展的设计, PolarDB PostgreSQL版Parallel DML支持一写多读、多写多读两种特性。
一写多读:在RO节点上有多个读Worker,在RW节点上只有一个写Worker。
多写多读:在RO节点上有多个读Worker,在RW节点上也有多个写Worker。多写多读场景下,读写的并发度完全解耦。
不同的特性适用不同的场景,用户可以根据自己的业务特点来选择不同的Parallel DML功能特性。
索引构建加速。
PolarDB PostgreSQL版分布式ePQ执行引擎,不仅可以用于只读查询和DML,还可以用于索引构建加速。OLTP业务中有大量的索引,而B-Tree索引创建的过程大约有80%的时间消耗在排序和构建索引页上,20%消耗在写入索引页上。如下图所示:PolarDB PostgreSQL版利用RO节点对数据进行分布式ePQ加速排序,采用流水化的技术来构建索引页,同时使用批量写入技术来提升索引页的写入速度。
说明在目前索引构建加速这一特性中,PolarDB PostgreSQL版已经支持了B-Tree索引的普通创建以及B-Tree索引的在线创建(Concurrently)两种功能。
使用指南
PolarDB PostgreSQL版ePQ适用于日常业务中的轻分析类业务,例如:对账业务,报表业务。
使用ePQ进行分析型查询。
PolarDB PostgreSQL版默认不开启ePQ功能。若您需要使用此功能,请使用如下参数:
参数
说明
polar_enable_px
指定是否开启ePQ功能。
on:开启ePQ功能。
off:(默认值)不开启ePQ功能。
polar_px_max_workers_number
设置单个节点上的最大ePQ Worker进程数,默认为30,取值范围为0~2147483647。该参数限制了单个节点上的最大并行度。
说明节点上所有会话的ePQ workers进程数不能超过该参数大小。
polar_px_dop_per_node
设置当前会话并行查询的并行度,默认为1,推荐值为当前CPU 总核数,取值范围为1~128。若设置该参数为N,则一个会话在每个节点上将会启用N个ePQ Worker进程,用于处理当前的ePQ逻辑。
polar_px_nodes
指定参与ePQ的只读节点。默认为空,表示所有只读节点都参与。可配置为指定节点参与ePQ,以逗号分隔。
px_worker
指定ePQ是否对特定表生效。默认不生效。ePQ功能比较消耗集群计算节点的资源,因此只有对设置了px_workers的表才使用该功能。例如:
ALTER TABLE t1 SET(px_workers=1):表示t1表允许ePQ。
ALTER TABLE t1 SET(px_workers=-1):表示t1表禁止ePQ。
ALTER TABLE t1 SET(px_workers=0):表示t1表忽略ePQ(默认状态)。
以简单的单表查询操作,来描述ePQ的功能是否有效。
创建test表并插入基础数据。
CREATE TABLE test(id int); INSERT INTO test SELECT generate_series(1,1000000);
查询执行计划。
EXPLAIN SELECT * FROM test;
执行结果如下:
QUERY PLAN -------------------------------------------------------- Seq Scan on test (cost=0.00..35.50 rows=2550 width=4) (1 row)
说明默认情况下ePQ功能不开启,单表查询执行计划为PG原生的Seq Scan。
开启并使用ePQ功能。
ALTER TABLE test SET (px_workers=1); SET polar_enable_px = on; EXPLAIN SELECT * FROM test;
结果如下:
QUERY PLAN ------------------------------------------------------------------------------- PX Coordinator 2:1 (slice1; segments: 2) (cost=0.00..431.00 rows=1 width=4) -> Seq Scan on test (scan partial) (cost=0.00..431.00 rows=1 width=4) Optimizer: PolarDB PX Optimizer (3 rows)
配置参与ePQ的计算节点范围。
查询当前所有只读节点的名称。
CREATE EXTENSION polar_monitor; SELECT name,host,port FROM polar_cluster_info WHERE px_node='t';
结果如下:
name | host | port -------+-----------+------ node1 | 127.0.0.1 | 5433 node2 | 127.0.0.1 | 5434 (2 rows)
说明当前集群有2个只读节点,名称分别为:node1,node2。
指定node1只读节点参与ePQ。
SET polar_px_nodes = 'node1';
查询参与并行查询的节点。
SHOW polar_px_nodes;
结果如下:
polar_px_nodes ---------------- node1 (1 row)
说明参与并行查询的节点是node1。
查询test表中所有数据。
EXPLAIN SELECT * FROM test;
结果如下:
QUERY PLAN ------------------------------------------------------------------------------- PX Coordinator 1:1 (slice1; segments: 1) (cost=0.00..431.00 rows=1 width=4) -> Partial Seq Scan on test (cost=0.00..431.00 rows=1 width=4) Optimizer: PolarDB PX Optimizer (3 rows) QUERY PLAN
使用ePQ进行分区表查询。
开启ePQ功能。
SET polar_enable_px = ON;
开启分区表ePQ功能。
SET polar_px_enable_partition = true;
说明分区表ePQ功能默认关闭。
开启多级分区表ePQ功能。
SET polar_px_optimizer_multilevel_partitioning = true;
当前ePQ对分区表支持的功能如下所示:
支持Range分区的并行查询。
支持List分区的并行查询。
支持单列Hash分区的并行查询。
支持分区裁剪。
支持带有索引的分区表并行查询。
支持分区表连接查询。
支持多级分区的并行查询。
使用ePQ加速索引创建。
开启使用ePQ加速创建索引功能。
SET polar_px_enable_btbuild = on;
创建索引。
CREATE INDEX t ON test(id) WITH(px_build = ON);
查询表结构。
\d test
结果如下:
Table "public.test" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | id2 | integer | | | Indexes: "t" btree (id) WITH (px_build=finish)
说明当前仅支持对B-Tree索引的构建,且暂不支持INCLUDE等索引构建语法,暂不支持表达式等索引列类型。
如果需要使用ePQ功能加速创建索引,请使用如下参数:
参数
说明
polar_px_dop_per_node
指定通过ePQ加速构建索引的并行度。默认为1,取值范围为1~128。
polar_px_enable_replay_wait
当使用ePQ加速索引构建时,当前会话内无需手动开启该参数,该参数将自动生效。以保证最近更新的数据表项可以被创建到索引中,保证索引表的完整性。索引创建完成后,该参数将会被重置为数据库默认值。
polar_px_enable_btbuild
是否开启使用ePQ加速创建索引。取值为OFF时不开启(默认),取值为ON时开启。
polar_bt_write_page_buffer_size
指定索引构建过程中的写I/O策略。该参数默认值为0(不开启),单位为块,最大值可设置为8192。推荐设置为4096。
参数值为0:该参数设置为不开启,在索引创建的过程中,对于索引页写满后的写盘方式是block-by-block的单个块写盘。
参数值不为0:该参数设置为开启,内核中将缓存一个polar_bt_write_page_buffer_size大小的buffer,对于需要写盘的索引页,会通过该buffer进行I/O合并再统一写盘,避免了频繁调度I/O带来的性能开销。该参数会额外提升20%的索引创建性能。